package com.sooth.micro.camunda.converter;

import com.sooth.common.core.exception.ServiceException;
import com.sooth.common.json.utils.JsonUtils;
import com.sooth.micro.camunda.converter.bean.BaseDefinition;
import com.sooth.micro.camunda.converter.bean.BpmnElementType;
import com.sooth.micro.camunda.converter.bean.ProcessModel;
import com.sooth.micro.camunda.converter.bean.event.start.StartEventDefinition;
import com.sooth.micro.camunda.converter.bean.extension.ButtonPerm;
import com.sooth.micro.camunda.converter.bean.extension.ExtBpmConstant;
import com.sooth.micro.camunda.converter.bean.extension.FormPerm;
import com.sooth.micro.camunda.converter.bean.gateway.BranchNode;
import com.sooth.micro.camunda.converter.bean.gateway.ExclusiveGatewayDefinition;
import com.sooth.micro.camunda.converter.bean.task.UserTaskDefinition;
import com.sooth.micro.camunda.converter.processing.BpmnElementProcessor;
import com.sooth.micro.camunda.converter.processing.BpmnElementProcessors;
import org.apache.commons.lang3.StringUtils;
import org.camunda.bpm.model.bpmn.Bpmn;
import org.camunda.bpm.model.bpmn.BpmnModelInstance;
import org.camunda.bpm.model.bpmn.builder.AbstractBaseElementBuilder;
import org.camunda.bpm.model.bpmn.builder.ProcessBuilder;
import org.camunda.bpm.model.bpmn.builder.StartEventBuilder;
import org.camunda.bpm.model.bpmn.instance.Process;
import org.camunda.bpm.model.bpmn.instance.*;
import org.camunda.bpm.model.bpmn.instance.camunda.CamundaProperty;

import java.util.*;

public class BpmnBuilder {

    public static BpmnModelInstance build(String json, String versionTag) {
        return build(ProcessModel.of(json), versionTag);
    }

    public static BpmnModelInstance build(ProcessModel processModel, String versionTag) {
        if (processModel == null) {
            return null;
        }
        try {
            ProcessBuilder executableProcess = Bpmn.createExecutableProcess();
            String processId = processModel.getProcessId();
            String processName = processModel.getProcessName();
            Optional.ofNullable(processName)
                    .filter(StringUtils::isNotBlank)
                    .ifPresent(executableProcess::name);
            Optional.ofNullable(processId)
                    .filter(StringUtils::isNotBlank)
                    .ifPresent(executableProcess::id);
            Optional.ofNullable(versionTag)
                    .filter(StringUtils::isNotBlank)
                    .ifPresent(executableProcess::camundaVersionTag);
            StartEventBuilder startEventBuilder = executableProcess.startEvent();
            BaseDefinition processNode = processModel.getProcessNode();
            BpmnElementProcessor<BaseDefinition, AbstractBaseElementBuilder> processor =
                    BpmnElementProcessors.getProcessor(BpmnElementType.START_EVENT);
            String lastNode = processor.onCreate(startEventBuilder, processNode);
            processor.moveToNode(startEventBuilder, lastNode).endEvent();
            BpmnModelInstance modelInstance = startEventBuilder.done();
            Bpmn.validateModel(modelInstance);
            return modelInstance;
        } catch (Exception e) {
            e.printStackTrace();
            throw new ServiceException("创建失败:" + e.getMessage());
        }
    }

    public static BaseDefinition expose(BpmnModelInstance modelInstance) {
        Collection<Process> processList = modelInstance.getModelElementsByType(Process.class);
        Map<String, BaseDefinition> definitionMap = new HashMap<>(processList.size() / 2 + 1);
        BaseDefinition baseDefinition = null;
        for (Process process : processList) {
            Collection<FlowElement> flowElementList = process.getFlowElements();
            for (FlowElement flowElement : flowElementList) {
                if (flowElement instanceof SequenceFlow) {
                    SequenceFlow sequenceFlow = (SequenceFlow) flowElement;
                    FlowNode source = sequenceFlow.getSource();
                    FlowNode target = sequenceFlow.getTarget();
                    BaseDefinition sourceDefinition = definitionMap.get(source.getId());
                    if (sourceDefinition == null) {
                        baseDefinition = sourceDefinition = getBaseDefinition(source, definitionMap);
                        definitionMap.put(source.getId(), sourceDefinition);

                    }
                    BaseDefinition targetDefinition = definitionMap.get(target.getId());
                    if (targetDefinition == null) {
                        targetDefinition = getBaseDefinition(target, definitionMap);
                        if (targetDefinition != null) {
                            definitionMap.put(target.getId(), targetDefinition);
                        }
                    }
                    if (targetDefinition != null && !BpmnElementType.EXCLUSIVE_GATEWAY.getElementTypeName().get().equals(sourceDefinition.getNodeType())) {
                        sourceDefinition.setNextNode(targetDefinition);
                    }
                }
            }
        }
        return baseDefinition;
    }

    public static BaseDefinition getBaseDefinition(FlowNode flowNode, Map<String, BaseDefinition> definitionMap) {
        if (flowNode instanceof StartEvent) {
            StartEvent startEvent = (StartEvent) flowNode;
            StartEventDefinition startEventDefinition = new StartEventDefinition();
            startEventDefinition.setPageId(Long.valueOf(startEvent.getCamundaFormKey()));
            wrap(startEventDefinition, flowNode);
            return startEventDefinition;
        } else if (flowNode instanceof UserTask) {
            UserTask userTask = (UserTask) flowNode;
            UserTaskDefinition userTaskDefinition = new UserTaskDefinition();
            userTaskDefinition.setPageId(Long.valueOf(userTask.getCamundaFormKey()));
            userTaskDefinition.setAssignee(userTask.getCamundaAssignee());
            userTaskDefinition.setCandidateUsers(userTask.getCamundaCandidateUsers());
            userTaskDefinition.setCandidateGroups(userTask.getCamundaCandidateGroups());
            wrap(userTaskDefinition, flowNode);
            return userTaskDefinition;
        } else if (flowNode instanceof ExclusiveGateway) {
            ExclusiveGateway exclusiveGateway = (ExclusiveGateway) flowNode;
            ExclusiveGatewayDefinition exclusiveGatewayDefinition = new ExclusiveGatewayDefinition();
            exclusiveGatewayDefinition.setId(flowNode.getId());
            exclusiveGatewayDefinition.setNodeName(flowNode.getName());
            Collection<SequenceFlow> outgoingList = exclusiveGateway.getOutgoing();
            List<BranchNode> branchNodes = new ArrayList<>(outgoingList.size());
            for (SequenceFlow sequenceFlow : outgoingList) {
                BranchNode branchNode = new BranchNode();
                ConditionExpression conditionExpression = sequenceFlow.getConditionExpression();
                String textContent = "";
                if (conditionExpression != null) {
                    textContent = conditionExpression.getTextContent();
                }
                CamundaProperty camundaProperty = (CamundaProperty) sequenceFlow.getExtensionElements().getElementsQuery().singleResult();
                boolean isDefault = "true".equals(camundaProperty.getAttributeValue(ExtBpmConstant.IS_DEFAULT));
                boolean isAnd = "true".equals(camundaProperty.getAttributeValue(ExtBpmConstant.IS_AND));
                String expressionType = camundaProperty.getAttributeValue(ExtBpmConstant.EXPRESSION_TYPE);
                List<BranchNode.BranchNodeColumn> expressionList = JsonUtils.parseArray(camundaProperty.getAttributeValue(ExtBpmConstant.EXPRESSION_List), BranchNode.BranchNodeColumn.class);
                branchNode.setNodeName(sequenceFlow.getName());
                branchNode.setExpression(textContent);
                branchNode.setIsDefault(isDefault);
                branchNode.setIsAnd(isAnd);
                branchNode.setExpressionType(expressionType);
                branchNode.setExpressionList(expressionList);
                FlowNode targetFlowNode = sequenceFlow.getTarget();
                if (targetFlowNode != null) {
                    BaseDefinition baseDefinition = definitionMap.get(targetFlowNode.getId());
                    if (baseDefinition == null) {
                        baseDefinition = getBaseDefinition(targetFlowNode, definitionMap);
                        definitionMap.put(baseDefinition.getId(), baseDefinition);
                    }
                    branchNode.setNextNode(baseDefinition);
                }
                branchNodes.add(branchNode);
            }
            exclusiveGatewayDefinition.setBranchNodes(branchNodes);
            return exclusiveGatewayDefinition;
        } else if (flowNode instanceof EndEvent) {
            return null;
        }
        throw new RuntimeException("不支持的流程节点类型：" + flowNode.getClass().getName());
    }

    public static void wrap(BaseDefinition baseDefinition, FlowNode flowNode) {
        baseDefinition.setId(flowNode.getId());
        baseDefinition.setNodeName(flowNode.getName());
        ExtensionElements extensionElements = flowNode.getExtensionElements();
        if (extensionElements != null) {
            CamundaProperty camundaProperty = (CamundaProperty) extensionElements.getElementsQuery().singleResult();
            if (camundaProperty != null) {
                baseDefinition.setButtonPermList(JsonUtils.parseArray(camundaProperty.getAttributeValue(ExtBpmConstant.BUTTON_PERM), ButtonPerm.class));
                baseDefinition.setFormPermList(JsonUtils.parseArray(camundaProperty.getAttributeValue(ExtBpmConstant.FORM_PERM), FormPerm.class));
            }
        }

    }
}
